package util.jobs;

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import models.CNAverage;
import models.NodeConfig;
import models.SensorConfig;
import play.Logger;
import play.jobs.Every;
import play.jobs.Job;
import play.jobs.On;
import util.PlausabilityCalculator;
import util.UnitConverter;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.sender.tinkerforge.TFMulticastSender;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;

@Every("15s")
public class PeriodicNotificationJob extends Job {

    private static final String UNKNOWN = "unbekannt";
    private static final long NOTIF_INTERVAL_MS = 60 * 1000;
    private static final String NEW_LINE = "\r\n";

    private TFMulticastSender sender = new REMSMulticastController().getMulticastSender();

    @Override
    public void doJob() throws Exception {
        List<PeriodicNotifictionMessageElement> messageElements = createMessageElements();
        String message = formatMessge(messageElements);
        sendMulticastMessage(message);
    }

    private void sendMulticastMessage(String message) {
        GatewayMessage gatewayMessage = new GatewayMessage(GateWayMessageType.PERIODIC_NOTIFICATION, message, null);
        Logger.info("Send Periodic Notification: %s", gatewayMessage.getMessage());
        sender.sendGateWayMessage(gatewayMessage);
    }

    private String formatMessge(List<PeriodicNotifictionMessageElement> messageElements) {
        String message = "";
        for (PeriodicNotifictionMessageElement periodicNotifictionMessageElement : messageElements) {
            message += periodicNotifictionMessageElement.getElementText();
            message += NEW_LINE;
        }
        return message;
    }

    private List<PeriodicNotifictionMessageElement> createMessageElements() {
        List<PeriodicNotifictionMessageElement> messageElements = new ArrayList<PeriodicNotifictionMessageElement>();
        List<TFSensorType> sensorTypes = SensorConfig.getConfiguredSensorTypeList();
        List<String> nodes = NodeConfig.getNodes();
        for (TFSensorType sensorType : sensorTypes) {
            AverageElement ae = getNotifIntervalAverage(sensorType, nodes);
            messageElements
                    .add(new PeriodicNotifictionMessageElement(sensorType, ae.getAverage(), ae.getPlausability()));
        }
        return messageElements;
    }

    private AverageElement getNotifIntervalAverage(TFSensorType sensorType, List<String> nodes) {
        long now = new Date().getTime();
        long startNotifIntervallTimeStamp = now - NOTIF_INTERVAL_MS;
        double numberOfResults = 0;
        ArrayList<CNAverage> averages = new ArrayList<CNAverage>();

        for (String node : nodes) {
            List<CNAverage> cnAverages = CNAverage.getCNAsince(startNotifIntervallTimeStamp, sensorType, node);
            if (cnAverages.size() >= 1) {
                numberOfResults++;
                averages.addAll(cnAverages);
            } else {
                Logger.warn("Not enough CNA ( %s ) for Node: ", node);
            }
        }
        Logger.debug("Periodic Notification, Number of restlis: %s", numberOfResults);
        if (numberOfResults == 0) {
            Logger.warn("NO CNA found, returning %s", UNKNOWN);
            return new AverageElement(UNKNOWN, 0);
        }
        Logger.debug("Calculate Notification Average for Type: %s on %s values", sensorType, averages.size());
        try {
            int sum = 0;
            double plausability = 0;
            for (CNAverage cnAverage : averages) {
                sum += cnAverage.getNodeValue();
                plausability += cnAverage.getPlausability();
            }
            
            // numberOfResults is not 0, we checked this before
            double factor = (double) numberOfResults / nodes.size();
            int value = (int) ((sum / averages.size()));
            double plausab = plausability / averages.size();
            int defPlaus = (int) (plausab * factor);
            String normValue = UnitConverter.convertTFValuetoNormValue(sensorType, value + "");
            return new AverageElement(normValue, defPlaus);
        } catch (Exception e) {
            Logger.error(e, "Calculate Notification Average failed, returning %s", UNKNOWN);
            return new AverageElement(UNKNOWN, 0);
        }
    }

    private class AverageElement {

        private String average;
        private int plausability;

        public AverageElement(String average, int plausability) {
            this.average = average;
            this.plausability = plausability;
        }

        public String getAverage() {
            return average;
        }

        public double getPlausability() {
            return plausability;
        }
    }

    private class PeriodicNotifictionMessageElement {

        private TFSensorType sensorType;
        private String value;
        private double plausability;

        public PeriodicNotifictionMessageElement(TFSensorType sensorType, String value, double plausability) {
            this.sensorType = sensorType;
            this.value = value;
            this.plausability = plausability;
        }

        private String getElementText() {
            return sensorType + ": " + value + " " + sensorType.getUnit() + " (" + plausability + "%)";
        }

        @Override
        public String toString() {
            return getElementText();
        }

    }

}
